/*
 * pwm.c
 *
 * Created: 28/01/2014 23:52:57
 *  Author: usuario
 */ 

#include <asf.h>
#include <dronus.h>

#include "pwms.h"

//  Due has 8 PWM channels
#define CHANNEL_0_PIN PIO_PC2_IDX
#define CHANNEL_1_PIN PIO_PC4_IDX
#define CHANNEL_2_PIN PIO_PC6_IDX
#define CHANNEL_3_PIN PIO_PC8_IDX
#define CHANNEL_4_PIN PIO_PC21_IDX
#define CHANNEL_5_PIN PIO_PC22_IDX
#define CHANNEL_6_PIN PIO_PC23_IDX
#define CHANNEL_7_PIN PIO_PC24_IDX

#define PWM_CHANNELS (PWM_CHANNEL0_ENABLED + PWM_CHANNEL1_ENABLED + PWM_CHANNEL2_ENABLED + PWM_CHANNEL3_ENABLED \
					+ PWM_CHANNEL4_ENABLED + PWM_CHANNEL5_ENABLED + PWM_CHANNEL6_ENABLED + PWM_CHANNEL7_ENABLED)


typedef struct pwm_t
{
	pwm_channel_t	pwm_channel;
} pwm_t;

pwm_t pwms[MAX_PWM];

int8_t	channels_to_pwm[PWM_CHANNELS];

uint8_t actived_pwm=0;

xSemaphoreHandle xSemaphore_pwm_mutex;
xSemaphoreHandle xSemaphore_pwm_notification;


void PWM_setup( void )
{
	pwm_clock_t clock_setting;
	
	configASSERT(PWM_CHANNEL0_ENABLED>=0);configASSERT(PWM_CHANNEL0_ENABLED<=1);
	configASSERT(PWM_CHANNEL1_ENABLED>=0);configASSERT(PWM_CHANNEL1_ENABLED<=1);
	configASSERT(PWM_CHANNEL2_ENABLED>=0);configASSERT(PWM_CHANNEL2_ENABLED<=1);
	configASSERT(PWM_CHANNEL3_ENABLED>=0);configASSERT(PWM_CHANNEL3_ENABLED<=1);
	configASSERT(PWM_CHANNEL4_ENABLED>=0);configASSERT(PWM_CHANNEL4_ENABLED<=1);
	configASSERT(PWM_CHANNEL5_ENABLED>=0);configASSERT(PWM_CHANNEL5_ENABLED<=1);
	configASSERT(PWM_CHANNEL6_ENABLED>=0);configASSERT(PWM_CHANNEL6_ENABLED<=1);
	configASSERT(PWM_CHANNEL7_ENABLED>=0);configASSERT(PWM_CHANNEL7_ENABLED<=1);
	configASSERT(MAX_PWM <= PWM_CHANNELS);

	pmc_enable_periph_clk(ID_PWM);
	
	clock_setting.ul_clka = PWM_FREQUENCY_LOW * PERIOD_VALUE;
	clock_setting.ul_clkb = PWM_FREQUENCY_HIGH * PERIOD_VALUE;
	clock_setting.ul_mck = sysclk_get_cpu_hz();
	pwm_init(PWM, &clock_setting);
	
	uint8_t	i;
	for (i=0;i<PWM_CHANNELS;i++)
		channels_to_pwm[i] = -1;
	for (i=0;i<MAX_PWM;i++)
		pwms[i].pwm_channel.channel=-1;
		
	xSemaphore_pwm_mutex = xSemaphoreCreateMutex();
	configASSERT(xSemaphore_pwm_mutex);
}

pwm_if PWM_conf (uint8_t channel, uint8_t pwm_frequency, uint32_t init_duty)
{
	//Check max pwms
	configASSERT(actived_pwm<MAX_PWM);
	
	//Check available channel
	uint8_t	i;
	for (i=0;i<MAX_PWM;i++)
		configASSERT(pwms[i].pwm_channel.channel!=channel);
		
	pwm_channel_disable(PWM, channel);
	
	switch (channel) {
	case 0:
		gpio_configure_pin(CHANNEL_0_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 1:
		gpio_configure_pin(CHANNEL_1_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 2:
		gpio_configure_pin(CHANNEL_2_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 3:
		gpio_configure_pin(CHANNEL_3_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 4:
		gpio_configure_pin(CHANNEL_4_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 5:
		gpio_configure_pin(CHANNEL_5_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 6:
		gpio_configure_pin(CHANNEL_6_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	case 7:
		gpio_configure_pin(CHANNEL_7_PIN, PIO_PERIPH_B | PIO_DEFAULT);
		break;
	}
			
	pwms[actived_pwm].pwm_channel.channel				= channel;
	pwms[actived_pwm].pwm_channel.ul_duty				= init_duty;
	pwms[actived_pwm].pwm_channel.alignment				= PWM_ALIGN_LEFT;
	pwms[actived_pwm].pwm_channel.polarity				= PWM_LOW;
	pwms[actived_pwm].pwm_channel.ul_period				= PERIOD_VALUE;
	if (pwm_frequency == PWM_FREQUENCY_LOW)
		  pwms[actived_pwm].pwm_channel.ul_prescaler		= PWM_CMR_CPRE_CLKA;
	else  pwms[actived_pwm].pwm_channel.ul_prescaler		= PWM_CMR_CPRE_CLKB;
	
	pwm_channel_init(PWM, &pwms[actived_pwm].pwm_channel);
	
	pwm_channel_enable_interrupt(PWM, channel, 0);
	NVIC_DisableIRQ(PWM_IRQn);
	NVIC_ClearPendingIRQ(PWM_IRQn);
	NVIC_SetPriority(PWM_IRQn, PWM_IRQ_PRIORITY);
	NVIC_EnableIRQ(PWM_IRQn);
	
	pwm_channel_enable(PWM, channel);
		
	channels_to_pwm[channel]=actived_pwm;
	
	actived_pwm++;
	
	return (pwm_if) &pwms[actived_pwm-1];
}

void PWM_update_duty_async(	pwm_if pwm, uint32_t duty,
								portTickType block_time_ticks, xSemaphoreHandle notification_semaphore)
{
	pwm_t	*ptr_pwm=(pwm_t *) pwm;
	
	xSemaphoreTake(xSemaphore_pwm_mutex, block_time_ticks);
	
	xSemaphore_pwm_notification=notification_semaphore;
	if (xSemaphore_pwm_notification!=NULL)
		xSemaphoreTake(xSemaphore_pwm_notification, (1000 / PWM_FREQUENCY_HIGH) * portTICK_RATE_MS);
		
	ptr_pwm->pwm_channel.ul_duty=duty;
}

void PWM_Handler(void)
{
	portBASE_TYPE higher_priority_task_woken = pdFALSE;
	uint32_t events = pwm_channel_get_interrupt_status(PWM);

	//if ((events & (1 << PWM_CHANNEL_3)) == (1 << PWM_CHANNEL_3))
	
	//update all the pwm at same time
	//taskENTER_CRITICAL();
	
#if defined(PWM_CHANNEL0_ENABLED)
	if ((events & 0x1))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[0]].pwm_channel,
								pwms[channels_to_pwm[0]].pwm_channel.ul_duty);
#endif
#if defined(PWM_CHANNEL1_ENABLED)	
	if ((events & 0x2))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[1]].pwm_channel,
								pwms[channels_to_pwm[1]].pwm_channel.ul_duty);
#endif								
#if defined(PWM_CHANNEL2_ENABLED)	
	if ((events & 0x4))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[2]].pwm_channel,
								pwms[channels_to_pwm[2]].pwm_channel.ul_duty);
#endif
#if defined(PWM_CHANNEL3_ENABLED)	
	if ((events & 0x8))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[3]].pwm_channel,
								pwms[channels_to_pwm[3]].pwm_channel.ul_duty);
#endif
#if defined(PWM_CHANNEL4_ENABLED)	
	if ((events & 0x10))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[4]].pwm_channel,
								pwms[channels_to_pwm[4]].pwm_channel.ul_duty);
#endif						
#if defined(PWM_CHANNEL5_ENABLED)	
	if ((events & 0x20))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[5]].pwm_channel,
								pwms[channels_to_pwm[5]].pwm_channel.ul_duty);
#endif
#if defined(PWM_CHANNEL6_ENABLED)	
	if ((events & 0x40))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[6]].pwm_channel,
								pwms[channels_to_pwm[6]].pwm_channel.ul_duty);
#endif
#if defined(PWM_CHANNEL7_ENABLED)	
	if ((events & 0x80))
		pwm_channel_update_duty(PWM, &pwms[channels_to_pwm[7]].pwm_channel,
								pwms[channels_to_pwm[7]].pwm_channel.ul_duty);
#endif								
	
	//taskEXIT_CRITICAL();
	
	if (xSemaphore_pwm_notification!=NULL)
		xSemaphoreGiveFromISR(xSemaphore_pwm_notification, &higher_priority_task_woken);
	
	xSemaphoreGiveFromISR(xSemaphore_pwm_mutex, &higher_priority_task_woken);
	
	
	portEND_SWITCHING_ISR(higher_priority_task_woken);
}

